home *** CD-ROM | disk | FTP | other *** search
/ MacHack 2000 / MacHack 2000.toast / pc / The Hacks / Dialing Addresses / Src / AddressTransfer.c < prev    next >
Encoding:
Text File  |  2000-06-23  |  46.4 KB  |  1,604 lines

  1. /*******************************************************************
  2.  Copyright © 1995 - 1998, 3Com Corporation or its subsidiaries ("3Com").  
  3.  All rights reserved.
  4.    
  5.  This software may be copied and used solely for developing products for 
  6.  the Palm Computing platform and for archival and backup purposes.  Except 
  7.  for the foregoing, no part of this software may be reproduced or transmitted 
  8.  in any form or by any means or used to make any derivative work (such as 
  9.  translation, transformation or adaptation) without express written consent 
  10.  from 3Com.
  11.  
  12.  3Com reserves the right to revise this software and to make changes in content 
  13.  from time to time without obligation on the part of 3Com to provide notification 
  14.  of such revision or changes.  
  15.  3COM MAKES NO REPRESENTATIONS OR WARRANTIES THAT THE SOFTWARE IS FREE OF ERRORS 
  16.  OR THAT THE SOFTWARE IS SUITABLE FOR YOUR USE.  THE SOFTWARE IS PROVIDED ON AN 
  17.  "AS IS" BASIS.  3COM MAKES NO WARRANTIES, TERMS OR CONDITIONS, EXPRESS OR IMPLIED, 
  18.  EITHER IN FACT OR BY OPERATION OF LAW, STATUTORY OR OTHERWISE, INCLUDING WARRANTIES, 
  19.  TERMS, OR CONDITIONS OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND 
  20.  SATISFACTORY QUALITY.
  21.  
  22.  TO THE FULL EXTENT ALLOWED BY LAW, 3COM ALSO EXCLUDES FOR ITSELF AND ITS SUPPLIERS 
  23.  ANY LIABILITY, WHETHER BASED IN CONTRACT OR TORT (INCLUDING NEGLIGENCE), FOR 
  24.  DIRECT, INCIDENTAL, CONSEQUENTIAL, INDIRECT, SPECIAL, OR PUNITIVE DAMAGES OF 
  25.  ANY KIND, OR FOR LOSS OF REVENUE OR PROFITS, LOSS OF BUSINESS, LOSS OF INFORMATION 
  26.  OR DATA, OR OTHER FINANCIAL LOSS ARISING OUT OF OR IN CONNECTION WITH THIS SOFTWARE, 
  27.  EVEN IF 3COM HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
  28.  
  29.  3Com, HotSync, Palm Computing, and Graffiti are registered trademarks, and 
  30.  Palm III and Palm OS are trademarks of 3Com Corporation or its subsidiaries.
  31.  
  32.  IF THIS SOFTWARE IS PROVIDED ON A COMPACT DISK, THE OTHER SOFTWARE AND 
  33.  DOCUMENTATION ON THE COMPACT DISK ARE SUBJECT TO THE LICENSE AGREEMENT 
  34.  ACCOMPANYING THE COMPACT DISK.
  35.  
  36.  *-------------------------------------------------------------------
  37.  * FileName:
  38.  *      AddressTransfer.c
  39.  *
  40.  * Description:
  41.  *      Address Book routines to transfer records.
  42.  *
  43.  * History:
  44.  *      4/24/97  roger - Created
  45.  *
  46.  *******************************************************************/
  47.  
  48.  
  49. // Set this to get to private database defines
  50. #define __ADDRMGR_PRIVATE__
  51.  
  52. #include <Pilot.h>
  53. #include <CharAttr.h>
  54. #include <Hardware.h>        // hwrDisplayWidth
  55.  
  56. #include "Address.h"
  57. #include "AddressRsc.h"
  58.  
  59.  
  60. #define identifierLengthMax        40
  61. #define addrFilenameExtension        "vcf"
  62. #define addrFilenameExtensionLength    3
  63.  
  64.  
  65. /***********************************************************************
  66.  *
  67.  * FUNCTION:        AddrRegisterData
  68.  *
  69.  * DESCRIPTION:        Register with the exchange manager to receive data
  70.  * with a certain name extension.
  71.  *
  72.  * PARAMETERS:        nothing
  73.  *
  74.  * RETURNED:        nothing
  75.  *
  76.  * REVISION HISTORY:
  77.  *            Name    Date        Description
  78.  *            ----    ----        -----------
  79.  *            rsf        12/2/97        Created
  80.  *
  81.  ***********************************************************************/
  82. extern void AddrRegisterData ()
  83. {
  84.     ExgRegisterData(sysFileCAddress, exgRegExtensionID, addrFilenameExtension);
  85. }
  86.  
  87.  
  88. /***********************************************************************
  89.  *
  90.  * FUNCTION:    GetChar
  91.  *
  92.  * DESCRIPTION: Function to get a character from the exg transport. 
  93.  *
  94.  * PARAMETERS:  exgSocketP - the exg connection
  95.  *
  96.  * RETURNED:    nothing
  97.  *
  98.  * REVISION HISTORY:
  99.  *         Name   Date      Description
  100.  *         ----   ----      -----------
  101.  *         roger   8/15/97   Initial Revision
  102.  *
  103.  ***********************************************************************/
  104. static Word GetChar(const void * exgSocketP)
  105. {
  106.     ULong len;
  107.     Byte c;
  108.     Err err;
  109.     
  110.     len = ExgReceive((ExgSocketPtr) exgSocketP, &c, sizeof(c), &err);
  111.     
  112.     if (err || len == 0)
  113.         return EOF;
  114.     else
  115.         return c;
  116. }
  117.  
  118.  
  119. /***********************************************************************
  120.  *
  121.  * FUNCTION:    PutString
  122.  *
  123.  * DESCRIPTION: Function to put a string to the exg transport. 
  124.  *
  125.  * PARAMETERS:  exgSocketP - the exg connection
  126.  *                     stringP - the string to put
  127.  *
  128.  * RETURNED:    nothing
  129.  *                If the all the string isn't sent an error is thrown using ErrThrow.
  130.  *
  131.  * REVISION HISTORY:
  132.  *         Name   Date      Description
  133.  *         ----   ----      -----------
  134.  *         roger   8/15/97   Initial Revision
  135.  *
  136.  ***********************************************************************/
  137. static void PutString(const void * exgSocketP, const Char * const stringP)
  138. {
  139.     ULong len;
  140.     Err err;
  141.     
  142.     len = ExgSend((ExgSocketPtr) exgSocketP, stringP, StrLen(stringP), &err);
  143.     
  144.     // If the bytes were not sent throw an error.
  145.     if (len == 0 && StrLen(stringP) > 0)
  146.         ErrThrow(err);
  147. }
  148.  
  149.  
  150. /***********************************************************************
  151.  *
  152.  * FUNCTION:    AddrSendRecordTryCatch
  153.  *
  154.  * DESCRIPTION: Send a record.  
  155.  *
  156.  * PARAMETERS:    dbP - pointer to the database to add the record to
  157.  *                 recordNum - the record number to send
  158.  *                 recordP - pointer to the record to send
  159.  *                 exgSocketP - the exchange socket used to send
  160.  *
  161.  * RETURNED:    0 if there's no error
  162.  *
  163.  * REVISION HISTORY:
  164.  *         Name   Date      Description
  165.  *         ----   ----      -----------
  166.  *         roger  12/11/97  Initial Revision
  167.  *
  168.  ***********************************************************************/
  169. static Err AddrSendRecordTryCatch (DmOpenRef dbP, Int recordNum, 
  170.     AddrDBRecordPtr recordP, ExgSocketPtr exgSocketP)
  171. {
  172.     Err error = 0;
  173.     
  174.     // An error can happen anywhere during the send process.  It's easier just to 
  175.     // catch the error.  If an error happens, we must pass it into ExgDisconnect.
  176.     // It will then cancel the send and display appropriate ui.
  177.     ErrTry
  178.         {
  179.         AddrExportVCard(dbP, recordNum, recordP, exgSocketP, PutString, true);
  180.         }
  181.     
  182.     ErrCatch(inErr)
  183.         {
  184.         error = inErr;
  185.         } ErrEndCatch
  186.     
  187.     
  188.     return error;
  189. }
  190.  
  191.  
  192. /***********************************************************************
  193.  *
  194.  * FUNCTION:    AddrSendRecord
  195.  *
  196.  * DESCRIPTION: Send a record.  
  197.  *
  198.  * PARAMETERS:    dbP - pointer to the database to add the record to
  199.  *                 recordNum - the record to send
  200.  *
  201.  * RETURNED:    true if the record is found and sent
  202.  *
  203.  * REVISION HISTORY:
  204.  *         Name   Date      Description
  205.  *         ----   ----      -----------
  206.  *         roger   5/9/97   Initial Revision
  207.  *
  208.  ***********************************************************************/
  209. extern void AddrSendRecord (DmOpenRef dbP, Int recordNum)
  210. {
  211.     AddrDBRecordType record;
  212.     Handle recordH;
  213.     Handle descriptionH;
  214.     UInt descriptionSize = 0;
  215.     SWord descriptionWidth;
  216.     Boolean descriptionFit;
  217.     UInt newDescriptionSize;
  218.     Handle nameH;
  219.     Err error;
  220.     ExgSocketType exgSocket;
  221.     
  222.     
  223.     // important to init structure to zeros...
  224.     MemSet(&exgSocket, sizeof(exgSocket), 0);
  225.     
  226.     // Form a description of what's being sent.  This will be displayed
  227.     // by the system send dialog on the sending and receiving devices.
  228.     error = AddrGetRecord (dbP, recordNum, &record, &recordH);
  229.     ErrNonFatalDisplayIf(error, "Can't get record");
  230.     
  231.     if (RecordContainsData(&record))
  232.         {
  233.         // Figure out whether a person's name or company should be displayed.
  234.         descriptionH = NULL;
  235.         exgSocket.description = NULL;
  236.         if (!(SortByCompany && record.fields[company]) &&
  237.             (record.fields[name] || record.fields[firstName]))
  238.             {
  239.             if (record.fields[name] && record.fields[firstName])
  240.                 descriptionSize = sizeOf7BitChar(' ') + sizeOf7BitChar('\0');
  241.             else
  242.                 descriptionSize = sizeOf7BitChar('\0');
  243.             
  244.             if (record.fields[name])
  245.                 descriptionSize += StrLen(record.fields[name]);
  246.             
  247.             if (record.fields[firstName])
  248.                 descriptionSize += StrLen(record.fields[firstName]);
  249.             
  250.             
  251.             descriptionH = MemHandleNew(descriptionSize);
  252.             if (descriptionH)
  253.                 {
  254.                 exgSocket.description = MemHandleLock(descriptionH);
  255.                 
  256.                 if (record.fields[firstName])
  257.                     {
  258.                     StrCopy(exgSocket.description, record.fields[firstName]);
  259.                     if (record.fields[name])
  260.                         StrCat(exgSocket.description, " ");
  261.                     }
  262.                 else
  263.                     exgSocket.description[0] = '\0';
  264.                 
  265.                 if (record.fields[name])
  266.                     {
  267.                     StrCat(exgSocket.description, record.fields[name]);
  268.                     }
  269.                 }
  270.             
  271.             }
  272.        else if (record.fields[company])
  273.              {
  274.             descriptionSize = StrLen(record.fields[company]) + sizeOf7BitChar('\0');
  275.             
  276.             descriptionH = MemHandleNew(descriptionSize);
  277.             if (descriptionH)
  278.                 {
  279.                 exgSocket.description = MemHandleLock(descriptionH);
  280.                 StrCopy(exgSocket.description, record.fields[company]);
  281.                 }
  282.             }
  283.         
  284.         // Truncate the description if too long
  285.         if (descriptionSize > 0)
  286.             {
  287.             // Make sure the description isn't too long.
  288.             newDescriptionSize = descriptionSize;
  289.             descriptionWidth = hwrDisplayWidth;
  290.             FntCharsInWidth (exgSocket.description, &descriptionWidth, (IntPtr)&newDescriptionSize, &descriptionFit);
  291.             
  292.             if (newDescriptionSize > 0)
  293.                 {
  294.                 newDescriptionSize += sizeOf7BitChar('\0');
  295.                 if (newDescriptionSize != descriptionSize)
  296.                     {
  297.                     exgSocket.description[newDescriptionSize] = '\0';
  298.                     MemHandleUnlock(descriptionH);
  299.                     MemHandleResize(descriptionH, newDescriptionSize + sizeOf7BitChar('\0'));
  300.                     exgSocket.description = MemHandleLock(descriptionH);
  301.                     }
  302.                 }
  303.             else
  304.                 {
  305.                 MemHandleFree(descriptionH);
  306.                 }
  307.             descriptionSize = newDescriptionSize;
  308.             }
  309.         
  310.         // Make a filename
  311.         if (descriptionSize > 0)
  312.             {
  313.             // Now make a filename from the description
  314.             nameH = MemHandleNew(imcFilenameLength);
  315.             exgSocket.name = MemHandleLock(nameH);
  316.             StrNCopy(exgSocket.name, exgSocket.description, 
  317.                 min(descriptionSize, imcFilenameLength - addrFilenameExtensionLength - sizeOf7BitChar('.')));
  318.             exgSocket.name[imcFilenameLength - 1 - addrFilenameExtensionLength - sizeOf7BitChar('.')] = nullChr;
  319.             StrCat(exgSocket.name, ".");
  320.             StrCat(exgSocket.name, addrFilenameExtension);
  321.             }
  322.         else
  323.             {
  324.             // A description is needed.  Either there never was one or the first line wasn't usable.
  325.             descriptionH = DmGetResource(strRsc, BeamDescriptionStr);
  326.             exgSocket.description = MemHandleLock(descriptionH);
  327.             
  328.             nameH = DmGetResource(strRsc, BeamFilenameStr);
  329.             exgSocket.name = MemHandleLock(nameH);
  330.             }
  331.         
  332.         
  333.         exgSocket.length = MemHandleSize(recordH) + 100;        // rough guess
  334.         exgSocket.target = sysFileCAddress;
  335.         error = ExgPut(&exgSocket);   // put data to destination
  336.         if (!error)
  337.             {
  338.             error = AddrSendRecordTryCatch(dbP, recordNum, &record, &exgSocket);
  339.  
  340.             ExgDisconnect(&exgSocket, error);
  341.             }
  342.         
  343.         // Clean up
  344.         if (descriptionH)
  345.             {
  346.             MemHandleUnlock (descriptionH);
  347.             if (MemHandleDataStorage (descriptionH))
  348.                 DmReleaseResource(descriptionH);
  349.             else
  350.                 MemHandleFree(descriptionH);
  351.             }
  352.         if (nameH)
  353.             {
  354.             MemHandleUnlock (nameH);
  355.             if (MemHandleDataStorage (nameH))
  356.                 DmReleaseResource(nameH);
  357.             else
  358.                 MemHandleFree(nameH);
  359.             }
  360.         }
  361.     else
  362.         FrmAlert(NoDataToBeamAlert);
  363.     
  364.     
  365.     MemHandleUnlock(recordH);
  366.     DmReleaseRecord(dbP, recordNum, false);
  367.     
  368.     
  369.     return;
  370. }
  371.  
  372.  
  373.  
  374.  
  375. /***********************************************************************
  376.  *
  377.  * FUNCTION:    AddrSendCategoryTryCatch
  378.  *
  379.  * DESCRIPTION: Send all visible records in a category.  
  380.  *
  381.  * PARAMETERS:    dbP - pointer to the database to add the record to
  382.  *                 categoryNum - the category of records to send
  383.  *                 exgSocketP - the exchange socket used to send
  384.  *                 index - the record number of the first record in the category to send
  385.  *
  386.  * RETURNED:    0 if there's no error
  387.  *
  388.  * REVISION HISTORY:
  389.  *         Name   Date      Description
  390.  *         ----   ----      -----------
  391.  *         roger   5/9/97   Initial Revision
  392.  *
  393.  ***********************************************************************/
  394. static Err AddrSendCategoryTryCatch (DmOpenRef dbP, Word categoryNum, 
  395.     ExgSocketPtr exgSocketP, Word index)
  396. {
  397.     volatile Err error = 0;
  398.     volatile Handle outRecordH = 0;
  399.     AddrDBRecordType outRecord;
  400.     
  401.     
  402.     // An error can happen anywhere during the send process.  It's easier just to 
  403.     // catch the error.  If an error happens, we must pass it into ExgDisconnect.
  404.     // It will then cancel the send and display appropriate ui.
  405.     ErrTry
  406.         {
  407.  
  408.         // Loop through all records in the category.
  409.         while (DmSeekRecordInCategory(dbP, &index, 0, dmSeekForward, categoryNum) == 0)
  410.             {
  411.             // Emit the record.  If the record is private do not emit it.
  412.             if (AddrGetRecord(dbP, index, &outRecord, (Handle*)&outRecordH) == 0)
  413.                 {
  414.                 AddrExportVCard(dbP, index, &outRecord, exgSocketP, PutString, true);
  415.                 
  416.                 MemHandleUnlock(outRecordH);
  417.                 }
  418.             
  419.             index++;
  420.             }
  421.         }
  422.     
  423.     ErrCatch(inErr)
  424.         {
  425.         error = inErr;
  426.         
  427.         if (outRecordH)
  428.             MemHandleUnlock(outRecordH);
  429.         } ErrEndCatch
  430.     
  431.     return error;    
  432. }
  433.  
  434.  
  435. /***********************************************************************
  436.  *
  437.  * FUNCTION:    AddrSendCategory
  438.  *
  439.  * DESCRIPTION: Send all visible records in a category.  
  440.  *
  441.  * PARAMETERS:    dbP - pointer to the database to add the record to
  442.  *                 categoryNum - the category of records to send
  443.  *
  444.  * RETURNED:    true if any records are found and sent
  445.  *
  446.  * REVISION HISTORY:
  447.  *         Name   Date      Description
  448.  *         ----   ----      -----------
  449.  *         roger   5/9/97   Initial Revision
  450.  *
  451.  ***********************************************************************/
  452. extern void AddrSendCategory (DmOpenRef dbP, Word categoryNum)
  453. {
  454.     Err error;
  455.     Char description[dmCategoryLength];
  456.     Word index;
  457.     Boolean foundAtLeastOneRecord;
  458.     ExgSocketType exgSocket;
  459.     UInt mode;
  460.     LocalID dbID;
  461.     UInt cardNo;
  462.     Boolean databaseReopened;
  463.     
  464.     
  465.     // If the database was opened to show secret records, reopen it to not see 
  466.     // secret records.  The idea is that secret records are not sent when a 
  467.     // category is sent.  They must be explicitly sent one by one.
  468.     DmOpenDatabaseInfo(dbP, &dbID, NULL, &mode, &cardNo, NULL);
  469.     if (mode & dmModeShowSecret)
  470.         {
  471.         dbP = DmOpenDatabase(cardNo, dbID, dmModeReadOnly);
  472.         databaseReopened = true;
  473.         }
  474.     else
  475.         databaseReopened = false;
  476.     
  477.     
  478.     // important to init structure to zeros...
  479.     MemSet(&exgSocket, sizeof(exgSocket), 0);
  480.     
  481.     
  482.     // Make sure there is at least one record in the category.
  483.     index = 0;
  484.     foundAtLeastOneRecord = false;
  485.     while (true)
  486.         {
  487.         if (DmSeekRecordInCategory(dbP, &index, 0, dmSeekForward, categoryNum) != 0)
  488.             break;
  489.         
  490.         foundAtLeastOneRecord = DmQueryRecord(dbP, index) != 0;
  491.         if (foundAtLeastOneRecord)
  492.             break;
  493.         
  494.         
  495.         index++;
  496.         }
  497.     
  498.     
  499.     // We should send the category because there's at least one record to send.
  500.     if (foundAtLeastOneRecord)
  501.         {
  502.         // Form a description of what's being sent.  This will be displayed
  503.         // by the system send dialog on the sending and receiving devices.
  504.         CategoryGetName (dbP, categoryNum, description);
  505.         exgSocket.description = description;
  506.         
  507.         // Now form a file name
  508.         exgSocket.name = MemPtrNew(StrLen(description) + sizeOf7BitChar('.') + StrLen(addrFilenameExtension) + sizeOf7BitChar('\0'));
  509.         if (exgSocket.name)
  510.             {
  511.             StrCopy(exgSocket.name, description);
  512.             StrCat(exgSocket.name, ".");
  513.             StrCat(exgSocket.name, addrFilenameExtension);
  514.             }
  515.         
  516.         exgSocket.length = 0;        // rough guess
  517.         exgSocket.target = sysFileCAddress;
  518.         error = ExgPut(&exgSocket);   // put data to destination
  519.         if (!error)
  520.             {
  521.             error = AddrSendCategoryTryCatch (dbP, categoryNum, &exgSocket, index);
  522.  
  523.             ExgDisconnect(&exgSocket, error);
  524.             }
  525.         }
  526.     else
  527.         FrmAlert(NoDataToBeamAlert);
  528.     
  529.     if (databaseReopened)
  530.         DmCloseDatabase(dbP);
  531.     
  532.     return;
  533. }
  534.  
  535.  
  536. /***********************************************************************
  537.  *
  538.  * FUNCTION:        ReceiveData
  539.  *
  540.  * DESCRIPTION:        Receives data into the output field using the Exg API
  541.  *
  542.  * PARAMETERS:        exgSocketP, socket from the app code
  543.  *                         sysAppLaunchCmdExgReceiveData
  544.  *
  545.  * RETURNED:        error code or zero for no error.
  546.  *
  547.  ***********************************************************************/
  548. extern Err AddrReceiveData(DmOpenRef dbP, ExgSocketPtr exgSocketP)
  549. {
  550.     volatile Err err;    
  551.     
  552.     // accept will open a progress dialog and wait for your receive commands
  553.     err = ExgAccept(exgSocketP);
  554.     
  555.     if (!err)
  556.         {
  557.         // Catch errors receiving records.  The import routine will clean up the
  558.         // incomplete record.  This routine passes the error to ExgDisconnect
  559.         // which displays appropriate ui.
  560.         ErrTry
  561.             {
  562.             // Keep importing records until it can't
  563.             while (AddrImportVCard(dbP, exgSocketP, GetChar, false, false))
  564.                 {};
  565.             }
  566.         
  567.         ErrCatch(inErr)
  568.             {
  569.             err = inErr;
  570.             } ErrEndCatch
  571.         
  572.         
  573.         ExgDisconnect(exgSocketP, err); // closes transfer dialog
  574.         }
  575.     
  576.     return err;
  577. }
  578.  
  579.  
  580. /***********************************************************************
  581.  *
  582.  * FUNCTION:        AddrSetGoToParams
  583.  *
  584.  * DESCRIPTION:    Store the information necessary to navigate to the 
  585.  *                record inserted into the launch code's parameter block.
  586.  *
  587.  * PARAMETERS:        dbP        - pointer to the database to add the record to
  588.  *                    exgSocketP - parameter block passed with the launch code
  589.  *                    uniqueID   - unique id of the record inserted
  590.  *
  591.  * RETURNED:        nothing
  592.  *
  593.  * REVISION HISTORY:
  594.  *            Name    Date        Description
  595.  *            ----    ----        -----------
  596.  *            art        10/17/97    Created
  597.  *
  598.  ***********************************************************************/
  599. static void AddrSetGoToParams (DmOpenRef dbP, ExgSocketPtr exgSocketP, DWord uniqueID)
  600. {
  601.     Word        recordNum;
  602.     UInt        cardNo;
  603.     LocalID     dbID;
  604.  
  605.     
  606.     if (! uniqueID) return;
  607.  
  608.     DmOpenDatabaseInfo (dbP, &dbID, NULL, NULL, &cardNo, NULL);
  609.  
  610.     // The this the the first record inserted, save the information
  611.     // necessary to navigate to the record.
  612.     if (! exgSocketP->goToParams.uniqueID)
  613.         {
  614.         DmFindRecordByID (dbP, uniqueID, &recordNum);
  615.  
  616.         exgSocketP->goToCreator = sysFileCAddress;
  617.         exgSocketP->goToParams.uniqueID = uniqueID;
  618.         exgSocketP->goToParams.dbID = dbID;
  619.         exgSocketP->goToParams.dbCardNo = cardNo;
  620.         exgSocketP->goToParams.recordNum = recordNum;
  621.         }
  622.  
  623.     // If we already have a record then make sure the record index 
  624.     // is still correct.  Don't update the index if the record is not
  625.     // in your the app's database.
  626.     else if (dbID == exgSocketP->goToParams.dbID &&
  627.                cardNo == exgSocketP->goToParams.dbCardNo)
  628.         {
  629.         DmFindRecordByID (dbP, exgSocketP->goToParams.uniqueID, &recordNum);
  630.  
  631.         exgSocketP->goToParams.recordNum = recordNum;
  632.         }
  633. }
  634.  
  635.  
  636. /************************************************************
  637.  *
  638.  * FUNCTION: ReplaceSemicolonsWithNewlines
  639.  *
  640.  * DESCRIPTION: Replace all semicolons in a string with newlines.
  641.  *
  642.  * PARAMETERS: 
  643.  *            stringP - pointer to a string
  644.  *
  645.  * RETURNS: nothing, the string is changed in place
  646.  *
  647.  *    REVISION HISTORY:
  648.  *            Name    Date        Description
  649.  *            ----    ----        -----------
  650.  *            rsf        12/2/97        Initial Revision
  651.  *
  652.  *************************************************************/
  653.  
  654. static void ReplaceSemicolonsWithNewlines(CharPtr stringP)
  655. {
  656.     CharPtr foundChrP;
  657.     
  658.     
  659.     foundChrP = stringP;
  660.     while ((foundChrP = StrChr(foundChrP, ';')) != NULL)
  661.         {
  662.         if (foundChrP == stringP || *(foundChrP - 1) != '\\')
  663.             *foundChrP = linefeedChr;
  664.         }    
  665. }
  666.  
  667.  
  668. /************************************************************
  669.  *
  670.  * FUNCTION: AddrImportVCard
  671.  *
  672.  * DESCRIPTION: Import a VCard record.
  673.  *
  674.  * PARAMETERS: 
  675.  *            dbP - pointer to the database to add the record to
  676.  *            inputStream    - pointer to where to import the record from
  677.  *            inputFunc - function to get input from the stream
  678.  *            obeyUniqueIDs - true to obey any unique ids if possible
  679.  *            beginAlreadyRead - whether the begin statement has been read
  680.  *
  681.  * RETURNS: true if the input was read
  682.  *
  683.  *    REVISION HISTORY:
  684.  *            Name    Date        Description
  685.  *            ----    ----        -----------
  686.  *            rsf    4/24/97    Initial Revision
  687.  *
  688.  *************************************************************/
  689.  
  690.      WordPtr MyGetCharAttr (void)
  691.             SYS_TRAP(sysTrapGetCharAttr);
  692.  
  693. extern Boolean 
  694. AddrImportVCard(DmOpenRef dbP, VoidPtr inputStream, GetCharF inputFunc, 
  695.     Boolean obeyUniqueIDs, Boolean beginAlreadyRead)
  696. {
  697.     Word c;
  698.     char identifier[identifierLengthMax];
  699.     char parameterName[identifierLengthMax];
  700.     int identifierEnd;
  701.     int i;
  702.     volatile AddrDBRecordType newRecord;
  703.     AddrDBPhoneFlags phoneFlags;
  704.     AddressPhoneLabels phoneLabel;
  705.     UInt indexNew;
  706.     UInt indexOld;
  707.     ULong uid;
  708.     WordPtr charAttrP;
  709.     Boolean lastCharWasQuotedPrintableContinueToNextLineChr;
  710.     Boolean quotedPrintable;
  711.     Err err;
  712.     CharPtr addressPostOfficeP, addressExtendedP;
  713.     DWord uniqueID;
  714.     volatile Err error = 0;
  715.     
  716.     
  717.     charAttrP = MyGetCharAttr();
  718.     
  719.     // Initialize a new record
  720.     for (i=0; i < addrNumFields; i++)
  721.         newRecord.fields[i] = NULL;    // clear the record
  722.     
  723.     newRecord.options.phones.phone1 = 0;    // Work
  724.     newRecord.options.phones.phone2 = 1;    // Home
  725.     newRecord.options.phones.phone3 = 2;    // Fax
  726.     newRecord.options.phones.phone4 = 7;    // Other
  727.     newRecord.options.phones.phone5 = 3;    // Email
  728.     newRecord.options.phones.displayPhoneForList = phone1 - firstPhoneField;
  729.     
  730.     uid = 0;
  731.     
  732.     // An error happens usually due to no memory.  It's easier just to 
  733.     // catch the error.  If an error happens, we remove the last record.  
  734.     // Then we throw a second time so the caller receives it and displays a message.
  735.     ErrTry
  736.         {
  737.         c = inputFunc(inputStream);
  738.         ImcReadWhiteSpace(inputStream, inputFunc, charAttrP, &c);
  739.         
  740.         if (c == EOF) return false;
  741.         
  742.         identifierEnd = 0;
  743.         // CAUTION: TxtCharIsAlpha(EOF) returns true
  744.         while (TxtCharIsAlpha(c) || c == ':' || 
  745.             (c != linefeedChr && c != 0x0D && TxtCharIsSpace(c)))
  746.             {
  747.             if (!TxtCharIsSpace(c) && (identifierEnd < identifierLengthMax-1))
  748.                  identifier[identifierEnd++] = (char) c;
  749.              
  750.             c = inputFunc(inputStream);
  751.             if (c == EOF) return false;
  752.             }
  753.         identifier[identifierEnd++] = nullChr;
  754.         
  755.         // Read in the vcard entry
  756.         if (StrCaselessCompare(identifier, "BEGIN:VCARD") == 0)
  757.             {
  758.             do
  759.                 {
  760.                 quotedPrintable = false;
  761.                 
  762.                 
  763.                 // Advance to the next line.  If the property wasn't handled we need to make sure that
  764.                 // if the property value contains quoted printable text that newlines within that 
  765.                 // section are skipped.
  766.                 lastCharWasQuotedPrintableContinueToNextLineChr = (c == '=');
  767.                 while ((c != endOfLineChr || lastCharWasQuotedPrintableContinueToNextLineChr) && c != EOF)
  768.                     {
  769.                     lastCharWasQuotedPrintableContinueToNextLineChr = (c == '=');
  770.                     c = inputFunc(inputStream);
  771.                     } 
  772.                 c = inputFunc(inputStream);
  773.                 
  774.                 // Skip the second part of a 0x0D 0x0A end of line sequence
  775.                 if (c == 0x0A)
  776.                     c = inputFunc(inputStream);
  777.                 else if (c == EOF)
  778.                     ErrThrow(exgErrBadData);
  779.                 
  780.                 
  781.                 // Read in an identifier.  Ignore any group specifier.
  782.                 do
  783.                     {
  784.                     // Consume any groupDelimeterChr
  785.                     if (c == groupDelimeterChr)
  786.                         c = inputFunc(inputStream);
  787.                     
  788.                     identifierEnd = 0;
  789.                     ImcReadWhiteSpace(inputStream, inputFunc, charAttrP, &c);
  790.                     while (c != valueDelimeterChr && c != parameterDelimeterChr && 
  791.                         c != groupDelimeterChr && c != endOfLineChr && c != EOF)
  792.                         {
  793.                         if (identifierEnd < identifierLengthMax)
  794.                             identifier[identifierEnd++] = (char) c;
  795.                         
  796.                         c = inputFunc(inputStream);
  797.                         }
  798.                     identifier[identifierEnd++] = nullChr;
  799.                     }
  800.                 while (c == groupDelimeterChr);
  801.                 
  802.                 
  803.                 // read in the decomposed name (this is the only required paramater in a vCard)
  804.                 if (StrCaselessCompare(identifier, "N") == 0)
  805.                 {
  806.                     // Free any memory already used because it's being replaced.
  807.                     if (newRecord.fields[name] != NULL)
  808.                         MemPtrFree(newRecord.fields[name]);
  809.                     if (newRecord.fields[firstName] != NULL)
  810.                         MemPtrFree(newRecord.fields[firstName]);
  811.  
  812.                     ImcSkipAllPropertyParameters(inputStream, inputFunc, &c, identifier, "edPrintable);
  813.  
  814.                     newRecord.fields[name] = ImcReadFieldNoSemicolon(inputStream, inputFunc, &c, tableMaxTextItemSize);
  815.                     if (c != endOfLineChr)    
  816.                         newRecord.fields[firstName] = ImcReadFieldNoSemicolon(inputStream, inputFunc, &c, tableMaxTextItemSize);
  817.                     
  818.                     // skip all other fields because we can't store them
  819.                 }
  820.                 
  821.                 // read in the decomposed address
  822.                 else if (StrCaselessCompare(identifier, "ADR") == 0)
  823.                     {
  824.                     // Free any memory already used because it's being replaced.
  825.                     if (newRecord.fields[address] != NULL) MemPtrFree(newRecord.fields[address]);
  826.                     if (newRecord.fields[city] != NULL) MemPtrFree(newRecord.fields[city]);
  827.                     if (newRecord.fields[state] != NULL) MemPtrFree(newRecord.fields[state]);
  828.                     if (newRecord.fields[zipCode] != NULL) MemPtrFree(newRecord.fields[zipCode]);
  829.                     if (newRecord.fields[country] != NULL) MemPtrFree(newRecord.fields[country]);
  830.                     
  831.                     ImcSkipAllPropertyParameters(inputStream, inputFunc, &c, identifier, "edPrintable);
  832.                     
  833.                     addressPostOfficeP = ImcReadFieldNoSemicolon(inputStream, inputFunc, &c, tableMaxTextItemSize);
  834.                     if (c != endOfLineChr)    
  835.                         addressExtendedP = ImcReadFieldNoSemicolon(inputStream, inputFunc, &c, tableMaxTextItemSize);
  836.                     if (c != endOfLineChr)    
  837.                         newRecord.fields[address] = ImcReadFieldNoSemicolon(inputStream, inputFunc, &c, tableMaxTextItemSize);
  838.                     if (c != endOfLineChr)    
  839.                         newRecord.fields[city] = ImcReadFieldNoSemicolon(inputStream, inputFunc, &c, tableMaxTextItemSize);
  840.                     if (c != endOfLineChr)    
  841.                         newRecord.fields[state] = ImcReadFieldNoSemicolon(inputStream, inputFunc, &c, tableMaxTextItemSize);
  842.                     if (c != endOfLineChr)    
  843.                         newRecord.fields[zipCode] = ImcReadFieldNoSemicolon(inputStream, inputFunc, &c, tableMaxTextItemSize);
  844.                     if (c != endOfLineChr)    
  845.                         newRecord.fields[country] = ImcReadFieldNoSemicolon(inputStream, inputFunc, &c, tableMaxTextItemSize);
  846.                     
  847.                     // Now include addressPostOfficeP and addressExtendedP in the address.
  848.                     if (addressPostOfficeP != NULL && addressPostOfficeP[0] != nullChr)
  849.                         {
  850.                         Handle stringH;
  851.                         Int newSize;
  852.                         
  853.                         if (newRecord.fields[address] != NULL)
  854.                             {
  855.                             // Resize addressPostOfficeP so that the address string can be copied after it. 
  856.                             newSize = min(tableMaxTextItemSize, StrLen(newRecord.fields[address])
  857.                                                                 + 2 + StrLen(addressPostOfficeP))
  858.                                 + sizeOf7BitChar(nullChr);
  859.                             stringH = (Handle) MemPtrRecoverHandle(addressPostOfficeP);
  860.                             MemHandleUnlock(stringH);
  861.                             MemHandleResize(stringH, newSize);
  862.                             addressPostOfficeP = (CharPtr) MemHandleLock(stringH);
  863.                             
  864.                             // Now add the address to the addressPostOfficeP, being careful not to overflow
  865.                             StrNCat(addressPostOfficeP, "\n ", tableMaxTextItemSize+1);
  866.                             StrNCat(addressPostOfficeP, newRecord.fields[address], tableMaxTextItemSize+1);
  867.                             
  868.                             // Append the string now that there's space
  869.                             MemPtrFree(newRecord.fields[address]);
  870.                             }
  871.                         
  872.                         // Use addressPostOfficeP for the address.
  873.                         newRecord.fields[address] = addressPostOfficeP;
  874.                         addressPostOfficeP = NULL;
  875.                         }
  876.                 
  877.                     if (addressExtendedP != NULL && addressExtendedP[0] != nullChr)
  878.                         {
  879.                         Handle stringH;
  880.                         Int newSize;
  881.                         
  882.                         if (newRecord.fields[address] != NULL)
  883.                             {
  884.                             // Resize addressExtendedP so that the address string can be copied after it. 
  885.                             newSize = min(tableMaxTextItemSize, StrLen(newRecord.fields[address])
  886.                                                                 + 2 + StrLen(addressExtendedP))
  887.                                         + sizeOf7BitChar(nullChr);
  888.                             stringH = (Handle) MemPtrRecoverHandle(addressExtendedP);
  889.                             MemHandleUnlock(stringH);
  890.                             MemHandleResize(stringH, newSize);
  891.                             addressExtendedP = (CharPtr) MemHandleLock(stringH);
  892.                             
  893.                             // Now add the address to the addressExtendedP, being careful not to overflow
  894.                             StrNCat(addressExtendedP, "\n ", tableMaxTextItemSize+1);
  895.                             StrNCat(addressExtendedP, newRecord.fields[address], tableMaxTextItemSize+1);
  896.                             
  897.                             // Append the string now that there's space
  898.                             MemPtrFree(newRecord.fields[address]);
  899.                             }
  900.                         
  901.                         // Use addressExtendedP for the address.
  902.                         newRecord.fields[address] = addressExtendedP;
  903.                         addressExtendedP = NULL;
  904.                         }
  905.                     }
  906.                 
  907.                 // read in the full name as the last name.  Do this only if the name field has been
  908.                 // set.  The idea is to make use of this info if the name isn't set.
  909.                 else if (StrCaselessCompare(identifier, "FN") == 0 &&
  910.                     newRecord.fields[name] == NULL)
  911.                     {
  912.                     ImcSkipAllPropertyParameters(inputStream, inputFunc, &c, identifier, "edPrintable);
  913.                     
  914.                     newRecord.fields[name] = ImcReadFieldQuotablePrintable(inputStream, inputFunc, &c, endOfLineChr, quotedPrintable, tableMaxTextItemSize);
  915.                     }
  916.                 
  917.                 // read in the decomposed company
  918.                 else if (StrCaselessCompare(identifier, "ORG") == 0)
  919.                     {
  920.                     // Free any memory already used because it's being replaced.
  921.                     if (newRecord.fields[company] != NULL) MemPtrFree(newRecord.fields[company]);
  922.                     
  923.                     ImcSkipAllPropertyParameters(inputStream, inputFunc, &c, identifier, "edPrintable);
  924.                     
  925.                     
  926.                     newRecord.fields[company] = ImcReadFieldQuotablePrintable(inputStream, inputFunc, &c, endOfLineChr, quotedPrintable, tableMaxTextItemSize);
  927.                     ReplaceSemicolonsWithNewlines(newRecord.fields[company]);
  928.                     }
  929.                 
  930.                 // read in the title
  931.                 else if (StrCaselessCompare(identifier, "TITLE") == 0)
  932.                     {
  933.                     // Free any memory already used because it's being replaced.
  934.                     if (newRecord.fields[title] != NULL) MemPtrFree(newRecord.fields[title]);
  935.                     
  936.                     ImcSkipAllPropertyParameters(inputStream, inputFunc, &c, identifier, "edPrintable);
  937.                     
  938.                     
  939.                     newRecord.fields[title] = ImcReadFieldQuotablePrintable(inputStream, inputFunc, &c, endOfLineChr, quotedPrintable, tableMaxTextItemSize);
  940.                     ReplaceSemicolonsWithNewlines(newRecord.fields[title]);
  941.                     }
  942.                 
  943.                 // read in the note
  944.                 else if (StrCaselessCompare(identifier, "NOTE") == 0)
  945.                     {
  946.                     // Free any memory already used because it's being replaced.
  947.                     if (newRecord.fields[note] != NULL) MemPtrFree(newRecord.fields[note]);
  948.                     
  949.                     ImcSkipAllPropertyParameters(inputStream, inputFunc, &c, identifier, "edPrintable);
  950.                     
  951.                     
  952.                     newRecord.fields[note] = ImcReadFieldQuotablePrintable(inputStream, inputFunc, &c, endOfLineChr, quotedPrintable, noteViewMaxLength);
  953.                     }
  954.                 
  955.                 // read in the unique identifier
  956.                 else if (StrCaselessCompare(identifier, "UID") == 0 && obeyUniqueIDs)
  957.                     {
  958.                     CharPtr uniqueIDStringP;
  959.                     ImcSkipAllPropertyParameters(inputStream, inputFunc, &c, identifier, "edPrintable);
  960.                     
  961.                     
  962.                     uniqueIDStringP = ImcReadFieldQuotablePrintable(inputStream, inputFunc, &c, endOfLineChr, quotedPrintable, tableMaxTextItemSize);
  963.                     if (uniqueIDStringP != NULL)
  964.                         {
  965.                         uid = StrAToI(uniqueIDStringP);
  966.                         MemPtrFree(uniqueIDStringP);
  967.                         
  968.                         // Check the uid for reasonableness.
  969.                         if (uid < (dmRecordIDReservedRange << 12))
  970.                             uid = 0;
  971.                         }
  972.                     }
  973.                 
  974.                 // read in the decomposed name
  975.                 else if (StrCaselessCompare(identifier, "TEL") == 0 ||
  976.                     StrCaselessCompare(identifier, "EMAIL") == 0)
  977.                     {
  978.                     // Read in phone fields until either no more
  979.                     // strings are available or no more spots exist to store custom
  980.                     // information.
  981.                     i = firstPhoneField;
  982.                     
  983.                     // Advance to the first empty field
  984.                     while (newRecord.fields[i] != NULL &&
  985.                         i <= lastPhoneField)
  986.                         {
  987.                         i++;
  988.                         }
  989.                     
  990.                     // If all the phone fields are used then don't read anymore
  991.                     if (i <= lastPhoneField)
  992.                         {
  993.                         phoneFlags.allBits = 0;
  994.                         
  995.                         // Is this an email address?
  996.                         if (StrCaselessCompare(identifier, "EMAIL") == 0)
  997.                             {
  998.                             phoneFlags.bits.email = true;
  999.                             }
  1000.                         
  1001.                         
  1002.                         // Parse the property parameters.
  1003.                         while (c != valueDelimeterChr)
  1004.                             {
  1005.                             c = inputFunc(inputStream);            // consume the valueDelimeterChr
  1006.                             if (c == EOF) break;
  1007.                             
  1008.                             ImcReadPropertyParameter(inputStream, inputFunc, &c, parameterName, identifier);
  1009.                             
  1010.                             if (StrCaselessCompare(identifier, "QUOTED-PRINTABLE") == 0)
  1011.                                 {
  1012.                                 quotedPrintable = true;
  1013.                                 }
  1014.                             else if (StrCaselessCompare(identifier, "HOME") == 0)
  1015.                                 {
  1016.                                 phoneFlags.bits.home = true;
  1017.                                 }
  1018.                             else if (StrCaselessCompare(identifier, "WORK") == 0)
  1019.                                 {
  1020.                                 phoneFlags.bits.work = true;
  1021.                                 }
  1022.                             else if (StrCaselessCompare(identifier, "FAX") == 0)
  1023.                                 {
  1024.                                 phoneFlags.bits.fax = true;
  1025.                                 }
  1026.                             else if (StrCaselessCompare(identifier, "PAGER") == 0)
  1027.                                 {
  1028.                                 phoneFlags.bits.pager = true;
  1029.                                 }
  1030.                             else if (StrCaselessCompare(identifier, "CELL") == 0 ||
  1031.                                 StrCaselessCompare(identifier, "CAR") == 0)
  1032.                                 {
  1033.                                 phoneFlags.bits.mobile = true;
  1034.                                 }
  1035.                             else if (StrCaselessCompare(identifier, "PREF") == 0)
  1036.                                 {
  1037.                                 newRecord.options.phones.displayPhoneForList = (i - firstPhoneField);
  1038.                                 }
  1039.                             }
  1040.                         c = inputFunc(inputStream);
  1041.                         
  1042.                         
  1043.                         // Analyze the phone label bits and pick a single label to best
  1044.                         // represent the bits.
  1045.                         if (phoneFlags.bits.email)
  1046.                             {
  1047.                             phoneLabel = emailLabel;
  1048.                             }
  1049.                         else if (phoneFlags.bits.fax)
  1050.                             {
  1051.                             phoneLabel = faxLabel;
  1052.                             }
  1053.                         else if (phoneFlags.bits.pager)
  1054.                             {
  1055.                             phoneLabel = pagerLabel;
  1056.                             }
  1057.                         else if (phoneFlags.bits.mobile)
  1058.                             {
  1059.                             phoneLabel = mobileLabel;
  1060.                             }
  1061.                         else if (phoneFlags.bits.home)
  1062.                             {
  1063.                             phoneLabel = homeLabel;
  1064.                             }
  1065.                         else if (phoneFlags.bits.work)
  1066.                             {
  1067.                             phoneLabel = workLabel;
  1068.                             }
  1069.                         else
  1070.                             {
  1071.                             phoneLabel = otherLabel;
  1072.                             }
  1073.  
  1074.                         SetPhoneLabel(&newRecord, i, phoneLabel);
  1075.                         
  1076.                         newRecord.fields[i] = ImcReadFieldQuotablePrintable(inputStream, inputFunc, &c,
  1077.                                                 parameterDelimeterChr, quotedPrintable, tableMaxTextItemSize);
  1078.                         }
  1079.                     
  1080.                     }
  1081.                 
  1082.                 // read in the custom field.  Note we could do intelligent matching into custom fields
  1083.                 // which have the same name in a different location.
  1084.                 else if (StrCaselessCompare(identifier, "X-PALM-CUSTOM") == 0)
  1085.                     {
  1086.                     UInt customNumber = 0;
  1087.                     Boolean numberSpecified = false;
  1088.                     
  1089.                     // Parse the property parameters.
  1090.                     while (c != valueDelimeterChr)
  1091.                         {
  1092.                         c = inputFunc(inputStream);            // consume the valueDelimeterChr
  1093.                         if (c == EOF) break;
  1094.                         
  1095.                         ImcReadPropertyParameter(inputStream, inputFunc, &c, parameterName, identifier);
  1096.                         
  1097.                         if (StrCaselessCompare(identifier, "QUOTED-PRINTABLE") == 0)
  1098.                             {
  1099.                             quotedPrintable = true;
  1100.                             }
  1101.                         // If we found a number and we don't have one already, then use the number for the category.
  1102.                         // This survives the case when the category name is a number.
  1103.                         else if (!numberSpecified && StrAToI(identifier) != 0)
  1104.                             {
  1105.                             customNumber = StrAToI(identifier) - 1;
  1106.                             numberSpecified = true;
  1107.                             }
  1108.  
  1109.                         }
  1110.                     c = inputFunc(inputStream);
  1111.                     
  1112.                     ErrNonFatalDisplayIf(customNumber >= (lastRenameableLabel - firstRenameableLabel + 1), 
  1113.                         "Invalid Custom Field");
  1114.                     
  1115.                     // Free any memory already used because it's being replaced.
  1116.                     if (newRecord.fields[custom1 + customNumber] != NULL) 
  1117.                         MemPtrFree(newRecord.fields[custom1 + customNumber]);
  1118.                     
  1119.                     newRecord.fields[custom1 + customNumber] = ImcReadFieldQuotablePrintable(inputStream, inputFunc, &c,
  1120.                                                                 endOfLineChr, quotedPrintable, tableMaxTextItemSize);
  1121.                     }
  1122.                 
  1123.                 // Begin a new vcard
  1124.                 else if (StrCaselessCompare(identifier, "BEGIN") == 0)
  1125.                     {
  1126.                     // Advance to the next line.
  1127.                     while (c != endOfLineChr && c != EOF)
  1128.                         {
  1129.                         c = inputFunc(inputStream);
  1130.                         } 
  1131.                     
  1132.                     if (c != EOF)
  1133.                         AddrImportVCard(dbP, inputStream, inputFunc, obeyUniqueIDs, true);
  1134.                     
  1135.                     break;
  1136.                     }
  1137.                 
  1138.                 // stop if at the end
  1139.                 else if (StrCaselessCompare(identifier, "END") == 0)
  1140.                     {
  1141.                     // Advance to the next line.
  1142.                     while (c != endOfLineChr && c != EOF)
  1143.                         {
  1144.                         c = inputFunc(inputStream);
  1145.                         } 
  1146.                     
  1147.                     break;
  1148.                     }
  1149.                 
  1150.                 } while (true);
  1151.  
  1152.         
  1153.             // Fix up the record's data.
  1154.             
  1155.             // If the displayed phone doesn't exist then pick the first phone which does.
  1156.             if (newRecord.fields[firstPhoneField + newRecord.options.phones.displayPhoneForList] == NULL)
  1157.                 for (i = firstPhoneField; i <= lastPhoneField; i++)
  1158.                     if (newRecord.fields[i] != NULL)
  1159.                         {
  1160.                         newRecord.options.phones.displayPhoneForList = i - firstPhoneField;
  1161.                         break;
  1162.                         }
  1163.  
  1164.             // if the company and name fields are identical, assume company only
  1165.             if (newRecord.fields[name] != NULL
  1166.                 && newRecord.fields[company] != NULL
  1167.                 && newRecord.fields[firstName] == NULL
  1168.                 && StrCompare(newRecord.fields[name], newRecord.fields[company]) == 0)
  1169.             {
  1170.                 MemPtrFree(newRecord.fields[name]);
  1171.                 newRecord.fields[name] = NULL;
  1172.             }
  1173.            
  1174.             err = AddrNewRecord(dbP, (AddrDBRecordType*)&newRecord, &indexNew);
  1175.             if (err)
  1176.                 ErrThrow(exgMemError);
  1177.             
  1178.             // If uid was set then a unique id was passed to be used.
  1179.             if (uid != 0 && obeyUniqueIDs)
  1180.                 {
  1181.                 // We can't simply remove any old record using the unique id and
  1182.                 // then add the new record because removing the old record could
  1183.                 // move the new one.  So, we find any old record, change the new
  1184.                 // record, and then remove the old one.
  1185.                 indexOld = indexNew;
  1186.                 
  1187.                 // Find any record with this uid.  indexOld changes only if 
  1188.                 // such a record is found.
  1189.                 DmFindRecordByID (dbP, uid, &indexOld);
  1190.                 
  1191.                 // Change this record to this uid.  The dirty bit is set from 
  1192.                 // newly making this record.
  1193.                 DmSetRecordInfo(dbP, indexNew, NULL, &uid);
  1194.                 
  1195.                 // Now remove any old record.
  1196.                 if (indexOld != indexNew)
  1197.                     {
  1198.                     DmRemoveRecord(dbP, indexOld);
  1199.                     }
  1200.                 }
  1201.  
  1202.  
  1203.             // Store the information necessary to navigate to the record inserted.
  1204.             DmRecordInfo(dbP, indexNew, NULL, &uniqueID, NULL);
  1205.  
  1206. #if EMULATION_LEVEL != EMULATION_NONE
  1207.             // Don't call AddrSetGoToParams for shell commands.  Do this by seeing which 
  1208.             // input function is passed - the one for shell commands or the local one for exchange.
  1209.             if (inputFunc == GetChar)
  1210. #endif
  1211.             AddrSetGoToParams (dbP, inputStream, uniqueID);
  1212.             }
  1213.         
  1214.         }
  1215.     
  1216.     ErrCatch(inErr)
  1217.         {
  1218.         // Throw the error after the memory is cleaned up.
  1219.         error = inErr;
  1220.         } ErrEndCatch
  1221.  
  1222.     
  1223.     // Free any temporary buffers used to store the incoming data.
  1224.     for (i=0; i < addrNumFields; i++)
  1225.         {
  1226.         if (newRecord.fields[i] != NULL)
  1227.             {
  1228.             MemPtrFree(newRecord.fields[i]);
  1229.             newRecord.fields[i] = NULL;    // clear the record
  1230.             }
  1231.         }
  1232.     
  1233.     if (error)
  1234.         ErrThrow(error);
  1235.     
  1236.     // don't worry about protecting c from ErrCatch because this code is never reached when ErrCatch is.
  1237.     return (c != EOF);
  1238. }
  1239.  
  1240.  
  1241. /************************************************************
  1242.  *
  1243.  * FUNCTION: AddrExportVCard
  1244.  *
  1245.  * DESCRIPTION: Export a record as a Imc VCard record
  1246.  *
  1247.  * PARAMETERS: 
  1248.  *            dbP - pointer to the database to export the records from
  1249.  *            index - the record number to export
  1250.  *            recordP - whether the begin statement has been read
  1251.  *            outputStream - pointer to where to export the record to
  1252.  *            outputFunc - function to send output to the stream
  1253.  *            writeUniqueIDs - true to write the record's unique id
  1254.  *
  1255.  * RETURNS: nothing
  1256.  *
  1257.  *    REVISION HISTORY:
  1258.  *            Name    Date        Description
  1259.  *            ----    ----        -----------
  1260.  *            rsf    8/6/97    Initial Revision
  1261.  *
  1262.  *************************************************************/
  1263.  
  1264. void AddrExportVCard(DmOpenRef dbP, Int index, AddrDBRecordType *recordP, 
  1265.     const VoidPtr outputStream, const PutStringF outputFunc, Boolean writeUniqueIDs)
  1266. {
  1267.     int            i;
  1268.     ULong            uid;
  1269.     AddrAppInfoPtr appInfoP= NULL;
  1270.     Char uidString[12];
  1271.     Boolean personOnlyAtHome = false;
  1272.     Boolean personOnlyAtWork = false;
  1273.     AddressPhoneLabels phoneLabel;
  1274.     Handle unnamedRecordStrH;
  1275.     CharPtr unnamedRecordStr;
  1276.  
  1277.     outputFunc(outputStream, "BEGIN:VCARD\015\012");
  1278.     
  1279.     // Emit a name
  1280.     if (recordP->fields[name] != NULL || 
  1281.         recordP->fields[firstName] != NULL)
  1282.         {
  1283.         // The item
  1284.         outputFunc(outputStream, "N");
  1285.         
  1286.         // Check if "CHARSET=ISO-8859-1" is needed
  1287.         if (!ImcStringIsAscii(recordP->fields[name]) || 
  1288.             !ImcStringIsAscii(recordP->fields[firstName]))
  1289.             {
  1290.             outputFunc(outputStream, ";CHARSET=ISO-8859-1");
  1291.             }
  1292.         
  1293.         outputFunc(outputStream, ":");
  1294.         
  1295.         ImcWriteNoSemicolon(outputStream, outputFunc, recordP->fields[name]);
  1296.         outputFunc(outputStream, ";");
  1297.         
  1298.         ImcWriteNoSemicolon(outputStream, outputFunc, recordP->fields[firstName]);
  1299.         
  1300.         outputFunc(outputStream, "\015\012");
  1301.         }
  1302.     else if (recordP->fields[company] != NULL)
  1303.         // no name field, so try emitting company in N: field
  1304.         {
  1305.         outputFunc(outputStream, "N");
  1306.         if (!ImcStringIsAscii(recordP->fields[company]))
  1307.             outputFunc(outputStream, ";CHARSET=ISO-8859-1");
  1308.         outputFunc(outputStream, ":");
  1309.         ImcWriteNoSemicolon(outputStream, outputFunc, recordP->fields[company]);
  1310.         outputFunc(outputStream, "\015\012");
  1311.         }
  1312.     else
  1313.         // no company name either, so emit unnamed identifier
  1314.         {
  1315.         unnamedRecordStrH = DmGetResource(strRsc, UnnamedRecordStr);
  1316.         unnamedRecordStr = MemHandleLock(unnamedRecordStrH);
  1317.  
  1318.         outputFunc(outputStream, "N");
  1319.         if (!ImcStringIsAscii(unnamedRecordStr))
  1320.             outputFunc(outputStream, ";CHARSET=ISO-8859-1");
  1321.         outputFunc(outputStream, ":");
  1322.         ImcWriteNoSemicolon(outputStream, outputFunc, unnamedRecordStr);
  1323.         outputFunc(outputStream, "\015\012");
  1324.  
  1325.         MemHandleUnlock(unnamedRecordStrH);
  1326.         DmReleaseResource(unnamedRecordStrH);   
  1327.         }
  1328.     
  1329.     
  1330.     // Emit an address
  1331.     if (recordP->fields[address] != NULL || 
  1332.         recordP->fields[city] != NULL || 
  1333.         recordP->fields[state] != NULL || 
  1334.         recordP->fields[zipCode] != NULL || 
  1335.         recordP->fields[country] != NULL)
  1336.         {
  1337.         // The item
  1338.         outputFunc(outputStream, "ADR");
  1339.         
  1340.         // Check if "CHARSET=ISO-8859-1" is needed
  1341.         if (!ImcStringIsAscii(recordP->fields[address]) || 
  1342.             !ImcStringIsAscii(recordP->fields[city]) || 
  1343.             !ImcStringIsAscii(recordP->fields[state]) || 
  1344.             !ImcStringIsAscii(recordP->fields[zipCode]) || 
  1345.             !ImcStringIsAscii(recordP->fields[country]))
  1346.             {
  1347.             outputFunc(outputStream, ";CHARSET=ISO-8859-1");
  1348.             }
  1349.         
  1350.         // Skip the P.O. Box field and the Extended Address field
  1351.         outputFunc(outputStream, ":;;");
  1352.         
  1353.         ImcWriteNoSemicolon(outputStream, outputFunc, recordP->fields[address]);
  1354.         outputFunc(outputStream, ";");
  1355.         
  1356.         ImcWriteNoSemicolon(outputStream, outputFunc, recordP->fields[city]);
  1357.         outputFunc(outputStream, ";");
  1358.         
  1359.         ImcWriteNoSemicolon(outputStream, outputFunc, recordP->fields[state]);
  1360.         outputFunc(outputStream, ";");
  1361.         
  1362.         ImcWriteNoSemicolon(outputStream, outputFunc, recordP->fields[zipCode]);
  1363.         outputFunc(outputStream, ";");
  1364.         
  1365.         ImcWriteNoSemicolon(outputStream, outputFunc, recordP->fields[country]);
  1366.         
  1367.         outputFunc(outputStream, "\015\012");
  1368.         }
  1369.     
  1370.     
  1371.     // Emit the organization
  1372.     if (recordP->fields[company] != NULL)
  1373.         {
  1374.         // The item
  1375.         outputFunc(outputStream, "ORG");
  1376.         
  1377.         ImcWriteQuotedPrintable(outputStream, outputFunc, recordP->fields[company], true);
  1378.         
  1379.         outputFunc(outputStream, "\015\012");
  1380.         }
  1381.     
  1382.     
  1383.     // Emit a title
  1384.     if (recordP->fields[title] != NULL)
  1385.         {
  1386.         // The item
  1387.         outputFunc(outputStream, "TITLE");
  1388.         
  1389.         ImcWriteQuotedPrintable(outputStream, outputFunc, recordP->fields[title], false);
  1390.         
  1391.         outputFunc(outputStream, "\015\012");
  1392.         }
  1393.     
  1394.     
  1395.     // Emit a note
  1396.     if (recordP->fields[note] != NULL)
  1397.         {
  1398.         // The item
  1399.         outputFunc(outputStream, "NOTE");
  1400.         
  1401.         ImcWriteQuotedPrintable(outputStream, outputFunc, recordP->fields[note], false);
  1402.         
  1403.         outputFunc(outputStream, "\015\012");
  1404.         }
  1405.     
  1406.     
  1407.     // First find out if only home or work phones are used
  1408.     for (i = firstPhoneField; i <= lastPhoneField; i++)
  1409.         {
  1410.         if (recordP->fields[i] != NULL)
  1411.             {
  1412.             phoneLabel = (AddressPhoneLabels) GetPhoneLabel(recordP, i);
  1413.             if (phoneLabel == homeLabel)
  1414.                 {
  1415.                 if (personOnlyAtWork)
  1416.                     {
  1417.                     personOnlyAtWork = false;
  1418.                     break;
  1419.                     }
  1420.                 else
  1421.                     {
  1422.                     personOnlyAtHome = true;
  1423.                     }
  1424.                 }
  1425.             else if (phoneLabel == workLabel)
  1426.                 {
  1427.                 if (personOnlyAtHome)
  1428.                     {
  1429.                     personOnlyAtHome = false;
  1430.                     break;
  1431.                     }
  1432.                 else
  1433.                     {
  1434.                     personOnlyAtWork = true;
  1435.                     }
  1436.                 }
  1437.  
  1438.             }
  1439.         }
  1440.     
  1441.     
  1442.     // Now emit the phone fields
  1443.     for (i = firstPhoneField; i <= lastPhoneField; i++)
  1444.         {
  1445.         if (recordP->fields[i] != NULL)
  1446.             {
  1447.             phoneLabel = (AddressPhoneLabels) GetPhoneLabel(recordP, i);
  1448.             if (phoneLabel != emailLabel)
  1449.                 {
  1450.                 // The item
  1451.                 outputFunc(outputStream, "TEL");
  1452.                 
  1453.                 // Is this prefered?  Assume so if listed in the list view.
  1454.                 if (recordP->options.phones.displayPhoneForList == i - firstPhoneField)
  1455.                     {
  1456.                     outputFunc(outputStream, ";PREF");
  1457.                     }
  1458.                 
  1459.                 if (personOnlyAtHome || phoneLabel == homeLabel)
  1460.                     outputFunc(outputStream, ";HOME");
  1461.                 else if (personOnlyAtWork || phoneLabel == workLabel)
  1462.                     outputFunc(outputStream, ";WORK");
  1463.  
  1464.                 switch (phoneLabel)
  1465.                     {
  1466.                     case faxLabel:
  1467.                         outputFunc(outputStream, ";FAX");
  1468.                         break;
  1469.                         
  1470.                     case pagerLabel:
  1471.                         outputFunc(outputStream, ";PAGER");
  1472.                         break;
  1473.                         
  1474.                     case mobileLabel:
  1475.                         outputFunc(outputStream, ";CELL");
  1476.                         break;
  1477.                         
  1478.                     case workLabel:
  1479.                     case homeLabel:
  1480.                     case mainLabel:
  1481.                         outputFunc(outputStream, ";VOICE");
  1482.                         break;
  1483.                     
  1484.                     case otherLabel:
  1485.                         break;
  1486.                     }
  1487.                 
  1488.                 
  1489.                 ImcWriteQuotedPrintable(outputStream, outputFunc, recordP->fields[i], false);
  1490.                 
  1491.                 outputFunc(outputStream, "\015\012");
  1492.                 }
  1493.             else
  1494.                 {
  1495.                 // The item
  1496.                 outputFunc(outputStream, "EMAIL");
  1497.                 
  1498.                 // Is this prefered?  Assume so if listed in the list view.
  1499.                 if (recordP->options.phones.displayPhoneForList == i - firstPhoneField)
  1500.                     {
  1501.                     outputFunc(outputStream, ";PREF");
  1502.                     }
  1503.                 
  1504.                 if (personOnlyAtHome)
  1505.                     outputFunc(outputStream, ";HOME");
  1506.                 else if (personOnlyAtWork)
  1507.                     outputFunc(outputStream, ";WORK");
  1508.  
  1509.                 // Now try to identify the email type by it's syntax.
  1510.                 // A '@' indicates a probable internet address.
  1511.                 if (StrChr(recordP->fields[i], '@') == NULL)
  1512.                     {
  1513.                     if (TxtCharIsDigit(recordP->fields[i][0]))
  1514.                         {
  1515.                         if (StrChr(recordP->fields[i], ',') != NULL)
  1516.                             outputFunc(outputStream, ";CIS");
  1517.                         else if (recordP->fields[i][3] == '-')
  1518.                             {
  1519.                             outputFunc(outputStream, ";MCIMail");
  1520.                             }
  1521.                         }
  1522.                     }
  1523.                 else
  1524.                     {
  1525.                     outputFunc(outputStream, ";INTERNET");
  1526.                     }
  1527.                 
  1528.                 
  1529.                 ImcWriteQuotedPrintable(outputStream, outputFunc, recordP->fields[i], false);
  1530.                 
  1531.                 outputFunc(outputStream, "\015\012");
  1532.                 }
  1533.             }
  1534.         }
  1535.     
  1536.     // Emit the custom fields
  1537.     for (i = firstRenameableLabel; i <= lastRenameableLabel; i++)
  1538.         {
  1539.         if (recordP->fields[i] != NULL)
  1540.             {
  1541.             // The item
  1542.             outputFunc(outputStream, "X-PALM-CUSTOM;");
  1543.             
  1544.             // Emit the custom field number
  1545.             StrIToA(uidString, i - firstRenameableLabel + 1);
  1546.             outputFunc(outputStream, uidString);
  1547.             
  1548.             if (appInfoP == NULL)
  1549.                 appInfoP = (AddrAppInfoPtr) AddrAppInfoGetPtr(dbP);
  1550.             
  1551.             // Emit the custom label name if used.  This will enable smart matching.
  1552.             if (appInfoP->fieldLabels[i][0] != nullChr)
  1553.                 {
  1554.                 Char temp[2];
  1555.                 CharPtr c;
  1556.                 
  1557.                 temp[1] = '\0';
  1558.                 
  1559.                 outputFunc(outputStream, ";");
  1560.                 
  1561.                 // Emit the custom field name.  Strip out all ':' and ';' chars because
  1562.                 // they have special meanings to VCard.
  1563.                 c = appInfoP->fieldLabels[i];
  1564.                 while (*c != nullChr)
  1565.                     {
  1566.                     if (*c != parameterDelimeterChr && *c != valueDelimeterChr)
  1567.                         {
  1568.                         temp[0] = *c;
  1569.                         outputFunc(outputStream, temp);
  1570.                         }
  1571.                     c++;
  1572.                     }
  1573.                 
  1574.                 }
  1575.             
  1576.             
  1577.             ImcWriteQuotedPrintable(outputStream, outputFunc, recordP->fields[i], false);
  1578.             
  1579.             outputFunc(outputStream, "\015\012");
  1580.             }
  1581.         }
  1582.     if (appInfoP != NULL)
  1583.         MemPtrUnlock(appInfoP);
  1584.     
  1585.     
  1586.     // Emit an unique id
  1587.     if (writeUniqueIDs)
  1588.         {
  1589.         // The item
  1590.         outputFunc(outputStream, "UID:");
  1591.         
  1592.         // Get the record's unique id and append to the string.
  1593.         DmRecordInfo(dbP, index, NULL, &uid, NULL);
  1594.         StrIToA(uidString, uid);
  1595.         outputFunc(outputStream, uidString);
  1596.         outputFunc(outputStream, "\015\012");
  1597.         }
  1598.     
  1599.     
  1600.     
  1601.     outputFunc(outputStream, "END:VCARD\015\012");
  1602. }
  1603.  
  1604.